﻿// This software is the confidential and proprietary information of NetBay.
// Such confidential information should not be disclosed.
// NetBay copyright(c) 2011

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.ServiceModel.DomainServices.Client;
using System.Text;
using GalaSoft.MvvmLight.Messaging;
using NetBay.Core.BusinessEntities;
using NetBay.Core.BusinessEntities.Workflow;
using NetBay.Core.BusinessEntities.Workflow.Rules;
using Rio.Applications.Web.RioWCFWorkflow;
using Rio.Applications.Web.Services;
using Rio.Framework.Localisation;
using Rio.Framework.Pages.AppCode.Events;
using Rio.Framework.Pages.AppCode.Extensions;
using Rio.Framework.Pages.Context;
using Rio.Framework.Pages.Controls.Models;
using Rio.Framework.Pages.Events;
using Rio.Framework.Pages.Workflow.Actions;
using Rio.Framework.Pages.Workflow.Models;

namespace Rio.Framework.Pages.Manager
{
    public class WorkflowManager : INotifyPropertyChanged, IDisposable
    {
        #region Delegates

        public delegate void FilterChangedHandler(object sender, WorkflowState newState);

        #endregion

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler Processing;
        public event EventHandler<ErrorEventArgs> Processed;
        public event EventHandler ActionsChanged;
        public event FilterChangedHandler FilterChanged;

        public event WorkflowHistoricEventHandler HistoricLoaded;

        public event WorkflowHistoricEventHandler HistoricForInstanceLoaded;

        public event WorkflowHistoricEventHandler HistoricForDocumentLoaded;

        #endregion

        #region Fields

        private ObservableCollection<ISelectableItem<WorkflowState>> _filters;
        private Folder _folder;
        private FolderType _folderType;
        private Organization _organization;
        private ObservableCollection<Document> _selectedDocuments;
        public WorkflowState? _selectedFilter;
        private WorkflowInstance _workflowInstance;
        private ObservableCollection<WorkflowItem> _workflowItems;
        private Rules _workflowRules;

        #endregion

        #region Properties

        public static Dictionary<WorkflowState, WorkflowStateInfo> WorkflowStatesInfos { get; set; }

        public Organization Organization
        {
            get { return _organization; }
            set
            {
                _organization = value;
                OnPropertyChanged("Organization");
            }
        }

        public FolderType FolderType
        {
            get { return _folderType; }
            set
            {
                _folderType = value;
                OnPropertyChanged("FolderType");
            }
        }

        public Folder Folder
        {
            get { return _folder; }
            set
            {
                _folder = value;
                OnPropertyChanged("Folder");
            }
        }

        public ObservableCollection<Document> SelectedDocuments
        {
            get { return _selectedDocuments; }
            set
            {
                _selectedDocuments = value;
                OnPropertyChanged("SelectedDocuments");

                UpdateActionsStates();
            }
        }

        [ImportMany(typeof(WorkflowButtonAction), AllowRecomposition = true)]
        public List<ExportFactory<WorkflowButtonAction>> Actions { get; set; }

        private List<WorkflowButtonAction> AvailableActions { get; set; }

        public WorkflowInstance WorkflowInstance
        {
            get { return _workflowInstance; }
            private set
            {
                _workflowInstance = value;
                OnPropertyChanged("WorkflowInstance");

                OnActionsChanged();
            }
        }

        public bool IsSendMode { get; private set; }

        public ObservableCollection<WorkflowItem> WorkflowItems
        {
            get { return _workflowItems; }
            set
            {
                _workflowItems = value;
                OnPropertyChanged("WorkflowItems");

                LoadFilters();

                OnActionsChanged();
                UpdateActionsStates();
            }
        }

        public Rules WorkflowRules
        {
            get { return _workflowRules; }
            private set
            {
                _workflowRules = value;
                OnPropertyChanged("WorkflowRules");

                UpdateActionsStates();
            }
        }

        public bool IsWorkflowFinished
        {
            get { return WorkflowInstance.IsEnded; }
        }

        public ObservableCollection<ISelectableItem<WorkflowState>> Filters
        {
            get { return _filters; }
            set
            {
                _filters = value;
                OnPropertyChanged("Filters");
            }
        }

        public WorkflowState? SelectedFilter
        {
            get
            {
                if (_selectedFilter == null)
                {
                    if (WorkflowInstance.IsEnded)
                    {
                        _selectedFilter = WorkflowState.All;
                    }
                    else
                    {
                        _selectedFilter = WorkflowState.None;
                    }
                }
                return _selectedFilter;
            }
            set
            {
                _selectedFilter = value;
                OnPropertyChanged("SelectedFilter");

                if (SelectedFilter != null)
                {
                    OnFilterChanged(SelectedFilter.Value);
                }
            }
        }

        public List<WorkflowAction> WorkflowDefinitionSelectedActions { get; private set; }

        #endregion

        #region Constructors

        public WorkflowManager()
        {
            AvailableActions = new List<WorkflowButtonAction>();
            try
            {
                CompositionInitializer.SatisfyImports(this);
            }
            catch (ReflectionTypeLoadException tLException)
            {
                var loaderMessages = new StringBuilder();
                loaderMessages.AppendLine(" Error while trying to load the assemblies: ");
                foreach (var loaderException in tLException.LoaderExceptions)
                {
                    loaderMessages.AppendLine(loaderException.Message);
                }

                throw  new Exception(loaderMessages.ToString());
            }
        }

        #endregion

        public void Initialize(WorkflowInstance workflowInstance, bool isSendMode)
        {
            WorkflowInstance = workflowInstance;
            IsSendMode = isSendMode;

            Messenger.Default.Register<WorkflowNotificationMessage>(this,
                                                                    WorkflowInstance.Identifier,
                                                                    ExecuteWorkflowAction);

            Update(false);
        }

        public static void Activate()
        {
            BusinessEntitiesDomainContext businessEntitiesDomainContext = GlobalWebContext.BusinessEntitiesDomainContext;
            var query = businessEntitiesDomainContext.GetWorkflowStateInfosQuery();
            businessEntitiesDomainContext.Load(
                query,
                result =>
                {
                    if (!result.HasError
                        && result.Entities != null)
                    {
                        WorkflowStatesInfos = new Dictionary<WorkflowState, WorkflowStateInfo>();

                        foreach (var info in result.Entities.Where(i => !WorkflowStatesInfos.ContainsKey(i.State)))
                        {
                            WorkflowStatesInfos.Add(info.State, info);
                        }
                    }
                },
                null);
        }

        private void Update(bool updateInstanceFirst)
        {
            OnProcessing();

            if (updateInstanceFirst)
            {
                BusinessEntitiesDomainContext businessEntitiesDomainContext =
                    GlobalWebContext.BusinessEntitiesDomainContext;
                var getWorkflowInstanceQuery =
                    businessEntitiesDomainContext.GetWorkflowInstanceQuery(WorkflowInstance.Identifier,
                                                                           GlobalWebContext.CurrentUser.UserId);
                businessEntitiesDomainContext.Load(getWorkflowInstanceQuery,
                                                   result =>
                                                   {
                                                       if (!result.HasError
                                                           && result.Entities.SingleOrDefault() != null)
                                                       {
                                                           WorkflowInstance = result.Entities.SingleOrDefault();

                                                           InternalUpdate();
                                                       }
                                                   },
                                                   null);
            }
            else
            {
                BusinessEntitiesDomainContext businessEntitiesDomainContext = GlobalWebContext.BusinessEntitiesDomainContext;
                var query = businessEntitiesDomainContext.GetDefinitionActionsQuery(WorkflowInstance.DefinitionId);
                businessEntitiesDomainContext.Load(
                    query,
                    result =>
                    {
                        if (!result.HasError && result.Entities != null)
                        {
                            WorkflowDefinitionSelectedActions = new List<WorkflowAction>(result.Entities);
                            InternalUpdate();
                        }
                    },
                    null);
            }
        }

        private void InternalUpdate()
        {
            LoadRules();

            LoadDocumentStates();

            OnProcessed();
        }

        private void LoadFilters()
        {
            if (WorkflowInstance != null
                && WorkflowItems != null)
            {
                Filters = new ObservableCollection<ISelectableItem<WorkflowState>>
                              {
                                  new SelectableItemViewModel<WorkflowState>(
                                      Resx.GetLabel(ResxWorkflow.WorkflowState_None),
                                      WorkflowState.None),
                                  new SelectableItemViewModel<WorkflowState>(
                                      Resx.GetLabel(ResxWorkflow.WorkflowState_All),
                                      WorkflowState.All),
                                 new SelectableItemViewModel<WorkflowState>(
                                      Resx.GetLabel(ResxWorkflow.WorkflowState_FolderDocuments),
                                      WorkflowState.AllDocumentsInFolder)
                              };

                foreach (var item in WorkflowItems.OfType<WorkflowDocument>())
                {
                    if (Filters.Where(p => p.Value == item.DocumentStatus).FirstOrDefault() == null)
                    {
                        Filters.Add(new SelectableItemViewModel<WorkflowState>(item.DocumentStatusLabel,
                                                                               item.DocumentStatus));
                    }
                }

                if (_selectedFilter == null)
                {
                    _selectedFilter = WorkflowInstance.IsEnded
                                         ? Filters.Where(i => i.Value == WorkflowState.All).Select(i => i.Value).
                                               SingleOrDefault()
                                         : Filters.Where(i => i.Value == WorkflowState.None).Select(i => i.Value).
                                               SingleOrDefault();
                }
            }
        }

        private void LoadDocumentStates()
        {
            BusinessEntitiesDomainContext businessEntitiesDomainContext = GlobalWebContext.BusinessEntitiesDomainContext;
            businessEntitiesDomainContext.GetDocumentState(WorkflowInstance,
                                                           result =>
                                                           {
                                                               if (!result.HasError)
                                                               {
                                                                   WorkflowItems =
                                                                       new ObservableCollection<WorkflowItem>(
                                                                           result.Value);
                                                               }
                                                           },
                                                           null);
        }

        private void LoadRules()
        {
            BusinessEntitiesDomainContext businessEntitiesDomainContext = GlobalWebContext.BusinessEntitiesDomainContext;
            businessEntitiesDomainContext.GetRulesByDefinitionId(
                WorkflowInstance.DefinitionId,
                delegate(InvokeOperation<Rules> rules)
                {
                    if (!rules.HasError)
                    {
                        WorkflowRules = rules.Value;
                        OnActionsChanged();
                    }
                },
                null);
        }

        public IEnumerable<WorkflowButtonAction> GetAvailableActions()
        {
            List<WorkflowButtonAction> returnActions = new List<WorkflowButtonAction>();

            if (WorkflowRules == null)
            {
                return returnActions;
            }

            if (WorkflowInstance == null)
            {
                return returnActions;
            }

            var currentWkfState = WorkflowRules.GetCurrentState(WorkflowInstance);

            if (currentWkfState != null)
            {
                //Get all available actions in rules for the current state of the instance
                var rulesActions = currentWkfState.GetAvailableRulesActions(IsSendMode);

                //Compare with all workflow actions
                foreach (var rulesAction in rulesActions)
                {
                    foreach (var action in Actions)
                    {
                        using (var workflowAction = action.CreateExport())
                        {
                            /*
                             * Check if action in rules and ui action match
                             * Check if we want a global action
                             * Check if the action has been selected in the manager
                             */

                            bool rulesActionIsSelected = true;

                            if (!IsSendMode)
                                rulesActionIsSelected =
                                    WorkflowDefinitionSelectedActions.Any(
                                        a => a.ActionOrConditionId == rulesAction.Identifier);

                            if (rulesAction.UiTypeAction == workflowAction.Value.UiTypeAction && (rulesActionIsSelected || rulesAction.IsOption))
                            {
                                if (!returnActions.Contains(workflowAction.Value)
                                && !(WorkflowRules.UseGlobalActionOnly && !workflowAction.Value.IsGlobalAction))
                                {
                                    workflowAction.Value.RulesAction = rulesAction;
                                    workflowAction.Value.WorkflowInstance = WorkflowInstance;
                                    returnActions.Add(workflowAction.Value);
                                }
                            }
                        }
                    }
                }
            }

            AvailableActions = returnActions.OrderByDescending(a => a.IsGlobalAction).ToList();

            return AvailableActions;
        }

        private void ExecuteWorkflowAction(WorkflowNotificationMessage n)
        {
            OnProcessing();

            long workflowInstanceId = WorkflowInstance.Identifier;
            long workflowAction = n.ActionId;
            long rguid = WorkflowInstance.RguId;
            List<long> documentsToProcess = null;

            if (!n.IsGlobalAction && SelectedDocuments != null
                && WorkflowItems != null)
            {
                documentsToProcess = (from workflowDocument in WorkflowItems.OfType<WorkflowDocument>()
                                      from selectedDocument in SelectedDocuments
                                      where selectedDocument.Identifier == workflowDocument.DocumentId
                                      select workflowDocument.Identifier).ToList();
            }

            BusinessEntitiesDomainContext businessEntitiesDomainContext =
                GlobalWebContext.BusinessEntitiesDomainContext;
            businessEntitiesDomainContext.ExecuteWorkflowAction(
                workflowInstanceId,
                workflowAction,
                rguid,
                documentsToProcess,
                n.UiActionType,
                n.Parameters,
                n.IsGlobalAction,
                Organization.Identifier,
                FolderType.Identifier,
                Folder.Identifier,
                ExecuteWorkflowActionCallBack,
                null);
        }

        private void ExecuteWorkflowActionCallBack(InvokeOperation<ActionResult> result)
        {
            OnProcessed(result.HasError, result.Error == null ? null : result.Error.Message);

            if (SelectedFilter != null)
            {
                OnFilterChanged(SelectedFilter.Value);
            }

            NotifyToRefreshFlowList();

            Update(true);
        }

        /// <summary>
        ///   Notify FlowPage that an action has occured and need to refresh list
        /// </summary>
        private void NotifyToRefreshFlowList()
        {
            Messenger.Default.Send(new NotificationMessage("refreshflowlist"));
        }

        private void UpdateActionsStates()
        {
            if (WorkflowItems == null)
                return;

            if (WorkflowRules == null)
                return;

            if (SelectedFilter == null)
            {
                UpdateButtonsStates(false, true, true);
                return;
            }

            if (SelectedDocuments == null || SelectedDocuments.Count <= 0)
            {
                UpdateButtonsStates(false, true, true);
                return;
            }

            if (SelectedFilter.Value != WorkflowState.All && SelectedFilter.Value != WorkflowState.None && SelectedFilter.Value != WorkflowState.AllDocumentsInFolder && !_workflowInstance.IsEnded)
            {
                UpdateButtonsStates(false, true, true);
                return;
            }

            if (SelectedFilter.Value == WorkflowState.All && SelectedDocuments.Count > 1 && !_workflowInstance.IsEnded)
            {
                UpdateButtonsStates(false, true, true);
                return;
            }

            OnProcessing();

            var workflowDocument =
                WorkflowItems.OfType<WorkflowDocument>().Where(
                    d => SelectedDocuments[0] != null && d.DocumentId == SelectedDocuments[0].Identifier).
                    SingleOrDefault();

            if (workflowDocument == null)
            {
                UpdateButtonsStates(false, true, true);
                OnProcessed();
                return;
            }

            if (workflowDocument.DocumentStatus == WorkflowState.None)
            {
                UpdateButtonsStates(true, true, true);
                OnProcessed();
                return;
            }

            UpdateButtonsStates(true, true, true);

            if (AvailableActions != null)
            {
                //Retrieve all available actions for this current state
                foreach (var workflowAction in AvailableActions)
                {
                    if (workflowAction != null)
                    {
                        //If the current action has occured on the crrent document
                        if (workflowDocument.LastActionId
                            == workflowAction.RulesAction.Identifier)
                        {
                            //ValidatingMode specifies that only one user can do the action
                            if (workflowAction.RulesAction.ValidatingMode == SelectionMode.One
                                && !workflowAction.IsGlobalAction)
                            {
                                workflowAction.IsEnabled = false;
                            }
                            else
                            {
                                WorkflowButtonAction currentWorkflowAction = workflowAction;

                                //Check if the current user has already do the action
                                BusinessEntitiesDomainContext businessEntitiesDomainContext =
                                    GlobalWebContext.BusinessEntitiesDomainContext;
                                businessEntitiesDomainContext.GetLastReceiversAction(
                                    WorkflowInstance.Identifier,
                                    WorkflowInstance.RguId,
                                    SelectedDocuments.First().Identifier,
                                    result =>
                                    {
                                        if (!result.HasError)
                                        {
                                            long lastActionId = result.Value.Value;

                                            /*
                                         * If the LastActionId is > 0 and match with the current action
                                         * Then the current user has do the action
                                         */

                                            if (lastActionId > 0
                                                &&
                                                lastActionId == currentWorkflowAction.RulesAction.Identifier
                                                && !currentWorkflowAction.IsGlobalAction)
                                            {
                                                currentWorkflowAction.IsEnabled = false;
                                            }
                                        }
                                    },
                                    null);
                            }
                        }
                        else
                        {
                            workflowAction.IsEnabled = true;
                        }
                    }
                }
            }

            OnProcessed();
        }

        private void UpdateButtonsStates(bool enable, bool includeGlobalActions, bool includeOptions)
        {
            if (AvailableActions != null)
            {
                List<WorkflowButtonAction> workflowButtonsAction = (from workflowButtonAction in AvailableActions
                                                                    where !workflowButtonAction.IsGlobalAction
                                                                    where !workflowButtonAction.RulesAction.IsOption
                                                                    select workflowButtonAction).ToList();

                if (includeGlobalActions)
                    workflowButtonsAction.AddRange(from workflowButtonAction in AvailableActions
                                                   where workflowButtonAction.IsGlobalAction
                                                   select workflowButtonAction);

                if (includeOptions)
                    workflowButtonsAction.AddRange(from workflowButtonAction in AvailableActions
                                                   where workflowButtonAction.RulesAction.IsOption
                                                   select workflowButtonAction);

                foreach (var action in workflowButtonsAction)
                {
                    action.IsEnabled = enable;
                }
            }
        }

        protected void OnProcessing()
        {
            if (Processing != null)
            {
                Processing(this, new EventArgs());
            }
        }

        protected void OnProcessed(bool hasError = false, string errorMessage = null)
        {
            if (Processed != null)
            {
                Processed(this, new ErrorEventArgs { HasError = hasError, ErrorMessage = errorMessage });
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void OnActionsChanged()
        {
            if (ActionsChanged != null)
            {
                ActionsChanged(this, new EventArgs());
            }
        }

        /// <summary>
        ///   Loads the historic.
        /// </summary>
        /// <param name = "folderType">Type of the folder.</param>
        /// <param name = "folder">The folder.</param>
        /// <param name = "sender"></param>
        public void LoadHistoric(FolderType folderType, Folder folder, object sender)
        {
            BusinessEntitiesDomainContext ds = GlobalWebContext.BusinessEntitiesDomainContext;
            var query = ds.GetWorkflowInstancesByFolderIdQuery(
                folder.FolderTypeIdentifier,
                folder.Identifier,
                GlobalWebContext.CurrentUser.UserId,
                GlobalWebContext.GetIsAdministrator(folderType));
            ds.Load(
                query,
                result => OnHistoricLoaded(result.Entities, result.UserState),
                sender);
        }

        /// <summary>
        ///   Loads the historic for instance.
        /// </summary>
        /// <param name = "instance">The instance.</param>
        /// <param name = "sender"></param>
        public void LoadHistoricForInstance(WorkflowInstance instance, object sender)
        {
            BusinessEntitiesDomainContext ds = GlobalWebContext.BusinessEntitiesDomainContext;
            ds.GetDocumentState(instance,
                                result => OnHistoricForInstanceLoaded(result.Value, result.UserState),
                                sender);
        }

        /// <summary>
        ///   Loads the historic for document.
        /// </summary>
        /// <param name = "document">The document.</param>
        /// <param name = "sender"></param>
        public void LoadHistoricForDocument(WorkflowDocument document, object sender)
        {
            BusinessEntitiesDomainContext ds = GlobalWebContext.BusinessEntitiesDomainContext;

            var query = ds.GetWorkflowHistoByDocIdQuery(
                document.InstanceId,
                document.DocumentId);

            ds.Load(
                query,
                result => OnHistoricForDocumentLoaded(result.Entities, result.UserState),
                sender);
        }

        protected void OnHistoricLoaded(IEnumerable<WorkflowInstance> historic, object sender)
        {
            if (HistoricLoaded != null)
            {
                HistoricLoaded(new WorkflowHistoricEventArgs(historic, sender));
            }
        }

        protected void OnHistoricForInstanceLoaded(IEnumerable<WorkflowItem> historicForInstance, object sender)
        {
            if (HistoricForInstanceLoaded != null)
            {
                HistoricForInstanceLoaded(new WorkflowHistoricEventArgs(historicForInstance, sender));
            }
        }

        protected void OnHistoricForDocumentLoaded(IEnumerable<WorkflowHisto> historicForDocument, object sender)
        {
            if (HistoricForDocumentLoaded != null)
            {
                HistoricForDocumentLoaded(new WorkflowHistoricEventArgs(historicForDocument, sender));
            }
        }

        protected void OnFilterChanged(WorkflowState newState)
        {
            if (FilterChanged != null)
            {
                FilterChanged(this, newState);
            }
        }

        public void Dispose()
        {
            Messenger.Default.Unregister<WorkflowNotificationMessage>(this, ExecuteWorkflowAction);
        }
    }
}